根本的原因是:数组在创建的时候必须知道内部元素的类型,而且一直都会记得这个类型信息,每次往数组里添加元素,都会做类型检查。
但因为Java泛型是用擦除(Erasure)实现的,运行时类型参数会被擦掉。比如:
List<String> l = new ArrayList<String>();
l.add("hello");
String str=l.get(0);
运行时,类型参数
List l = new ArrayList();
l.add("hello");
String str=(String)List.get(0);
所以,如果像下面这样初始化泛型数组的话,
List<String>[] l = new ArrayList<String>[10]; //Error
运行时编译器只能看到ArrayList,而看不到泛型的String类型参数。数组由于无法确定所持有元素的类型,所以不允许初始化。
Java Language Specification明确规定:数组内的元素必须是“物化”的。
It is a compile-time error if the component type of the array being initialized is not reifiable.
对“物化”的第一条定义就是不能是泛型:
A type is reifiable if and only if one of the following holds: It refers to a non-generic class or interface type declaration. … …
因为Array的具体实现是在虚拟机层面,嵌地非常深,也查不到源码。只好用javap反编译看看具体初始化数组的字节码。我们反编译下面一段代码:初始化一个String数组和一个int数组。
String[] s=new String[]{"hello"};
int[] i=new int[]{1,2,3};
反编译的片段如下:
Code:
0: iconst_1
1: anewarray #2 // class java/lang/String
4: dup
5: iconst_0
6: ldc #3 // String hello
8: aastore
9: astore_1
10: iconst_3
11: newarray int
13: dup
14: iconst_0
15: iconst_1
... ...
其中:
anewarray和newarray都是虚拟机内部用来创建数组的命令。最多只能有2的8次方256个操作码,光创建数组就占了不止一个,可见数组的地位有多特殊。
其中newarray用atype来标记数组类型。anewarray用index来标记。从描述里可以看到,数组除了元素类型,还有一个必须确定的是长度,因为数组是一段连续内存。
查一下 Java Virtual Machine 对anewarray命令的描述,
**anewarray
** indicates what types of object references are to be stored in the array. It is either the name of a class or interface, e.g. java/lang/String, or, to create the first dimension of a multidimensional array, can be an array type descriptor, e.g.[Ljava/lang/String;
比如anewarray字节码命令的格式就是anewarray后面跟一个具体的元素类型。所以不能确定
最初在还没有泛型的时候,数组就一直是以“The Special One”的形象出现。那个时候所有容器都还只是持有Object。在其他容器都不太关心类型安全的年代,数组就特立独行地坚持类型检查,它的使命就是提供一个“类型安全”和效率更高的容器。所以类型检查和长度限制都被写到了字节码的规范里。至于到了支持泛型的年代,泛型“泛化”的本质就和数组“精确高效”的主旨根本上是相违背的。而且要改的话就要在字节码里动刀了。还是历史包袱的问题。